package main

import (
	"fmt"
	log "github.com/YoungPioneers/blog4go"
	"github.com/kardianos/service"
	"godenycc/funcs"
	"os"
	"path/filepath"
	"strings"
	"time"
)

var log_config, configFileName string

//初始化几个配置文件的路径
func init() {
	godenycc_path := GetCurrentDirectory()

	//日志的配置文件路径，默认当前程序目录下的log_config.xml文件
	log_config = godenycc_path + "/log_config.xml"

	//godenycc配置文件路径，默认当前程序目录下的godenycc_config.xml文件
	configFileName = godenycc_path + "/godenycc_config.ini"
}

//获取当前路径
func GetCurrentDirectory() string {
	dir, err := filepath.Abs(filepath.Dir(os.Args[0])) //返回绝对路径  filepath.Dir(os.Args[0])去除最后一个元素的路径
	if err != nil {
		log.Error(err)
	}
	return strings.Replace(dir, "\\", "/", -1) //将\替换成/
}

func main() {
	// ---------------------------------------------1)加载日志配置文件---------------------------------------------
	//log_config := "./log_config.xml"
	//log_config = "./log_config.xml"
	err := log.NewWriterFromConfigAsFile(log_config)
	if nil != err {
		fmt.Println(err.Error())
		os.Exit(1)
	}
	defer log.Close()
	// ---------------------------------------------1)daemons---------------------------------------------
	//服务的配置信息
	cfg := &service.Config{
		Name:        "godenycc",
		DisplayName: "godenycc is a simple service",
		Description: "godenycc is a simple ccfirewall.",
	}
	// Interface 接口
	prg := &program{}
	// 构建服务对象
	s, err := service.New(prg, cfg)
	if err != nil {
		log.Error(err)
	}
	// logger 用于记录系统日志
	logger, err := s.Logger(nil)
	if err != nil {
		log.Error(err)
	}
	if len(os.Args) == 2 { //如果有命令则执行
		err = service.Control(s, os.Args[1])
		if err != nil {
			log.Error(err)
		}
	} else { //否则说明是方法启动了
		err = s.Run()
		if err != nil {
			logger.Error(err)
		}
	}
	if err != nil {
		logger.Error(err)
	}

}

type program struct{}

func (p *program) Start(s service.Service) error {
	log.Info("开始服务")
	go p.run()
	return nil
}
func (p *program) Stop(s service.Service) error {
	log.Info("停止服务")
	return nil
}
func (p *program) run() {

	log.Debug("开始执行run")
	//	需要执行的任务
	//	1)读取配置文件
	white_ip_list, concurrency_limit_int, black_ip_list, block_time_int, ipset_black_list_name, sleep_time_int := funcs.LoadDenyccConfig(configFileName)
	log.Info("获取的white_ip_list：", white_ip_list)
	log.Info("获取的concurrency_limit_int：", concurrency_limit_int)
	log.Info("获取的black_ip_list：", black_ip_list)
	log.Info("获取的block_time_int：", block_time_int)
	log.Info("获取的ipset_black_list_name：", ipset_black_list_name)
	log.Info("获取的sleep_time_int：", sleep_time_int)
	for {
		time.Sleep(time.Duration(sleep_time_int) * time.Second)
		denyccBegin(white_ip_list, concurrency_limit_int, black_ip_list, block_time_int, ipset_black_list_name)
	}

}

//开始cc检查
func denyccBegin(white_ip_list string, concurrency_limit_int int, black_ip_list string, block_time_int int, ipset_black_list_name string) {

	// ---------------------------------------------3)开始检查---------------------------------------------
	//	3.1)到这里应该是通过ss -an也就是showstatus函数来查看tcp链接数信息了。但是在执行该函数之前，我们需要获取白名单。
	//  通过白名单我们只获取那些需要检查的ip地址。
	// 上面已经获取了白名单（忽略IP列表）的文件路径名，那么现在需要读取该文件中的ip地址了。

	//读取白名单列表，获取白名单
	log.Debug("开始执行Read_while_list_file 获取白名单")
	white_ip_str := ""
	white_ip := funcs.Read_while_list_file(white_ip_list) //不要忘记，这里的white_ip是个切片
	//如果这个切片长度为0，即白名单为空,如果不为0，则需要将白名单切片转换为127.0.0.1|10.0.10.20|192.168.1.20这样的字符串
	if len(white_ip) != 0 {
		white_ip_str = funcs.Slice2String(white_ip)
	}

	//同样的我们需要再获取一下黑名单中的IP地址，因为已经存在于黑名单的IP地址，再ss -an检查时，这些IP地址的链接数有可能
	//还是超过并发链接数的，但是因为已经存在于黑名单中，所以我们再次检查的时候不需要再对黑名单的这些IP再次进行阻止操作了。
	log.Debug("白名单(需要过滤的IP)：", white_ip_str)

	//3.2)既然已经获得了白名单ip地址，那么开始showstatus操作.获取tcp链接数的结果
	log.Debug("开始执行 ShowStatus")
	ssan_out := funcs.ShowStatus(white_ip_str)
	log.Debug("ShowStatus结果\n",ssan_out)
	//	3.3）既然结果有了，接下来要判断哪些结果符合拉黑条件。那就是所谓的检测了。使用CC_check
	//经过CC_check之后，bad_ip会被添加到ipset list中，且会写到ip阻止列表中
	log.Debug("开始执行 CC_check")
	funcs.CC_check(ssan_out, concurrency_limit_int, black_ip_list, ipset_black_list_name)
	//	3.4）阻止了之后，随后要解除阻止，使用unbanip函数来解除阻止的IP地址
	log.Debug("开始执行 Unbanip")

	funcs.Unbanip(black_ip_list, int64(block_time_int), ipset_black_list_name)
	log.Debug("GoDenycc 一个循环结束")
}
